/*
 * DIM-SUM操作系统 -- 早期内存映射，用于串口访问
 *
 * Copyright (C) 2017 谢宝友
 *
 * 作者: Baoyou Xie <baoyou.xie@aliyun.com>
 *
 * License terms: GNU General Public License (GPL) version 3
 *
 */

#include <dim-sum/boot_allotter.h>
#include <dim-sum/cache.h>
#include <dim-sum/init.h>
#include <dim-sum/memory_regions.h>
#include <dim-sum/mm_types.h>
#include <dim-sum/mmu.h>
#include <dim-sum/sched.h>
#include <dim-sum/string.h>

#include <asm/early_map.h>
#include <asm/mmu_context.h>
#include <asm/memory.h>


static inline mmu_l3_t *follow_mmu_l3(struct memory_map_desc *desc, unsigned long addr)
{
	mmu_l2_t* l2, *out_l2, l2v;
	mmu_l3_t* l3, *out_l3, l3v;
	l2 = mmu_l2_wptr((mmu_l1_t*)(desc->pt_l1), addr);
	l2v = (mmu_l2_t)(*l2);
	if (mmu_l2_is_empty(l2v)) {
		BUG();
	} else {
		out_l2 = (mmu_l2_t*)linear_phys_to_virt((virt_addr_t)((l2v >> 10) << PAGE_SHIFT));
	}
	
	l3 = mmu_l3_wptr(out_l2, addr);
	l3v = (mmu_l3_t)(*l3);
	if (mmu_l3_is_empty(l3v)) {
		BUG();
	} else {
		out_l3 = (mmu_l3_t*)linear_phys_to_virt((virt_addr_t)((l3v >> 10) << PAGE_SHIFT));
	}
	return out_l3;
}

static inline mmu_l4_t *follow_mmu_l4(struct memory_map_desc *desc, unsigned long addr)
{
	mmu_l3_t* out_l3;
	mmu_l4_t* out_l4;
	out_l3 = follow_mmu_l3(desc, addr);
	BUG_ON(!out_l3);
	out_l4 = mmu_l4_wptr(out_l3, addr);
	return out_l4;
}

static inline void map_set_entry(void *write,
	virt_addr_t p, u64 attr, int level)
{
	phys_addr_t phys, pfn;
	
	if (level == 1)
		return;
	/**
	 * 现有配置下只需要2、3、4级
	 */
	pfn = p >> PAGE_SHIFT;
	phys = (pfn << 10) | (phys_addr_t)attr;

	if (level == 2) {
		*((mmu_l2_t *)write) = (mmu_l2_t)phys;
		return;
	}
	if (level == 3) {
		*((mmu_l3_t *)write) = (mmu_l3_t)phys;
		return;
	}
	if (level == 4) {
		*((mmu_l4_t *)write) = (mmu_l4_t)phys;
		return;
	}
	return;
}

/**
 * 设置early-map页面属性
 */
static void early_map_set_pt_l4(int idx, phys_addr_t phys, page_attr_t flags)
{
	unsigned long addr = early_map_to_virt(idx);
	mmu_l4_t* out_l4;
	BUG_ON(idx <= EARLY_MAP_BEGIN || idx >= EARLY_MAP_END);

	out_l4 = follow_mmu_l4(&kern_memory_map, addr);

	if (page_attr_val(flags)) {
		map_set_entry(out_l4, phys, flags, 4);
	} else {
		map_set_entry(out_l4, 0, 0, 4);
		flush_tlb_kernel_range(addr, addr+PAGE_SIZE);
	}
	flush_cache_all();
}

void early_mapping(int idx, phys_addr_t phys,
		enum early_map_page_type type)
{
	page_attr_t attr;

	switch (type) {
	case EARLY_MAP_IO:
		attr = EARLY_MAP_PAGEATTR_IO;
		break;
	case EARLY_MAP_KERNEL:
		attr = EARLY_MAP_PAGEATTR_KERNEL;
		break;
	case EARLY_MAP_NOCACHE:
		attr = EARLY_MAP_PAGEATTR_NOCACHE;
		break;
	case EARLY_MAP_CLEAR:
		attr = EARLY_MAP_PAGEATTR_NONE;
		break;
	default:
		BUG();
	}

	early_map_set_pt_l4(idx, phys, attr);
}

void __init init_early_map_early(void)
{
	mmu_l2_t* l2, *out_l2, l2v;
	mmu_l3_t* l3, *out_l3, l3v;
	virt_addr_t l2_n, l3_n;
	phys_addr_t l2_n_phys, l3_n_phys;
	unsigned long addr = EARLY_MAP_VA_START;

	l2 = mmu_l2_wptr((mmu_l1_t*)(kern_memory_map.pt_l1), addr);
	l2v = (mmu_l2_t)(*l2);
	if (mmu_l2_is_empty(l2v)) {
		l2_n = (virt_addr_t)alloc_boot_mem_permanent(PAGE_SIZE, PAGE_SIZE);
		if (!l2_n)
			BUG();
		l2_n_phys = linear_virt_to_phys(l2_n);
		map_set_entry(l2, l2_n_phys, 1, 2);
		out_l2 = (mmu_l2_t*)l2_n;
	} else {
		out_l2 = (mmu_l2_t*)linear_phys_to_virt((virt_addr_t)((l2v >> 10) << PAGE_SHIFT));
	}
	
	l3 = mmu_l3_wptr(out_l2, addr);
	l3v = (mmu_l3_t)(*l3);
	if (mmu_l3_is_empty(l3v)) {
		l3_n = (virt_addr_t)alloc_boot_mem_permanent(PAGE_SIZE, PAGE_SIZE);
		if (!l3_n)
			BUG();
		l3_n_phys = linear_virt_to_phys(l3_n);
		map_set_entry(l3, l3_n_phys, 1, 3);
		out_l3 = (mmu_l3_t *)l3_n;
	} else {
		out_l3 = (mmu_l3_t*)linear_phys_to_virt((virt_addr_t)((l3v >> 10) << PAGE_SHIFT));
	}

	flush_tlb_all();

	/**
	 * 目前只用了一个页面来进行L4级映射
	 * 因此所有early-map必须位于一个L3级映射之内
	 */
	if ((early_map_to_virt(EARLY_MAP_BEGIN) >> MMU_L2_SHIFT)
		     != (early_map_to_virt(EARLY_MAP_END) >> MMU_L2_SHIFT)) {
		BUG();
	}

	if ((out_l3 != follow_mmu_l3(&kern_memory_map,
				early_map_to_virt(EARLY_MAP_BEGIN)))
	    || out_l3 != follow_mmu_l3(&kern_memory_map,
				early_map_to_virt(EARLY_MAP_END))) {
		BUG();
	}

}